package baidu

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/pkg/errors"
)

const (
	baiduAccessKey   = "754917b65f54483782040b332205ded3"
	baiduSecretKey   = "d7f519ce54c04ad9813d704879711176"
	baiduTokengetUrl = "https://aip.baidubce.com/oauth/2.0/token"
)

type param struct {
	key   string
	value string
}

type paramSlice []param

func newParamList() paramSlice {
	return make([]param, 0)
}

func (p *paramSlice) AddParam(key string, value string) {
	var v param

	v.key = key
	v.value = value

	*p = append(*p, v)
}

// 重写 Len() 方法
func (a paramSlice) Len() int {
	return len(a)
}

// 重写 Swap() 方法
func (a paramSlice) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

// 重写 Less() 方法， 从小到大排序
func (a paramSlice) Less(i, j int) bool {
	return a[i].key < a[j].key
}

/*

Host: bj.bcebos.com
Date: Mon, 27 Apr 2015 16:23:49 +0800
Content-Type: text/plain
Content-Length: 8

*/

func uriEncode(input string) {

	result := ""
	for i := 0; i < len(input); i++ {
		ch := input[i]
		if (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' || ch == '.' {
			result = fmt.Sprintf("%s%c", result, ch)
		} else if ch == '/' {

		} else {

		}
	}
	u := url.Values{}
	u.Encode()
	s, _ := url.ParseRequestURI("")

	s.EscapedPath()

}

func addParamBaidu(key string, header http.Header, u *url.Values) string {

	k := strings.ToLower(key)
	value := strings.ToLower(header.Get(key))

	if len(value) > 0 {
		u.Add(k, value)

		return k
	}

	return ""
}

func CanonicalRequest(uri *url.URL, header http.Header) string {

	u := url.Values{}

	signedHeaders := addParamBaidu("Host", header, &u)
	signedHeaders += addParamBaidu("Content-Type", header, &u)
	signedHeaders += addParamBaidu("Content-Length", header, &u)

	canonicalHeaders := u.Encode()

	fmt.Printf("signed old headers:%v\n", canonicalHeaders)
	newCanonicalHeaders := strings.Replace(canonicalHeaders, "&", "\n", -1)

	fmt.Printf("signed headers:%v\n", newCanonicalHeaders)

	canonicalRequest := "post" + "\n" + uri.RequestURI() + "\n" + uri.RawQuery + "\n" + newCanonicalHeaders

	fmt.Printf("canonicalRequest:%v\n", canonicalRequest)
	return canonicalRequest
}

func Sha256Encode(s string) string {
	h := sha256.New()
	io.WriteString(h, s)
	r := h.Sum(nil)

	return hex.EncodeToString(r[:])
}

func HmacSha256Encode(key, data string) string {

	mac := hmac.New(sha256.New, []byte(key))
	mac.Write([]byte(data))
	r := mac.Sum(nil)
	return hex.EncodeToString(r[:])

}

func SingedKeyCalte(uri *url.URL, header http.Header) (string, string) {

	authStringPrefix := fmt.Sprintf("bce-auth-v1/%s/%s/%v", baiduAccessKey, time.Now().Format("2006-01-02T15:04:05Z"), 1800)
	SigningKey := HmacSha256Encode(baiduSecretKey, authStringPrefix)

	canReq := CanonicalRequest(uri, header)

	signature := HmacSha256Encode(SigningKey, canReq)

	return signature, authStringPrefix
}

func Request(body []byte) ([]byte, error) {

	client := &http.Client{
		Transport: &http.Transport{
			Dial: func(netw, addr string) (net.Conn, error) {
				conn, err := net.DialTimeout(netw, addr, time.Second*5)
				if err != nil {
					return nil, err
				}

				return conn, nil
			},
			ResponseHeaderTimeout: time.Second * 5,
			MaxIdleConnsPerHost:   10,
			MaxIdleConns:          100,
		},
	}

	reqBody := bytes.NewBuffer(body)

	//reqUrl := "https://aip.baidubce.com/rpc/2.0/nlp/v1/sentiment_classify"
	reqUrl := "https://bj.bcebos.com/v1/test/myfolder/readme.txt?partNumber=9&uploadId=a44cc9bab11cbd156984767aad637851"
	req, err := http.NewRequest("POST", reqUrl, reqBody)
	if nil != err {

		return nil, err
	}

	uri, _ := url.Parse(reqUrl)
	//req.Header.Set("Host", "aip.baidubce.com")
	req.Header.Set("Host", uri.Host)
	req.Header.Set("Content-Type", "application/json;charset=utf-8")

	len := fmt.Sprintf("%d", len(body))
	req.Header.Set("Content-Length", len)

	sign, authPrefix := SingedKeyCalte(uri, req.Header)

	authStr := authPrefix + "//" + sign
	req.Header.Set("Authorization", authStr)

	//req.Header.Write(os.Stdout)

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	if 200 != resp.StatusCode {
		return nil, errors.New("return status code not 200")
	}

	resbody, err := ioutil.ReadAll(resp.Body)
	return resbody, err
}
